/**
 *  @file StreamReader.cpp
 */
#include "StreamReader.h"
#include "../common/CppMemPool.h"
#include "../common/UtilFuncT.h"
#include "Stream.h"
#include "../exceptions/BaseException.h"
#include "../collections/BinBuf.h"

using namespace cppflib::collections;
using namespace cppflib::exceptions;

namespace cppflib
{

namespace io
{

PRIVATE const wc_t INVALID_CHAR = static_cast<wc_t>(0xffff);

PRIVATE void __ClearCharBuf(wc_t buf[2])
{
   buf[0] = buf[1] = INVALID_CHAR;
}

StreamReader::StreamReader(void)
{
   this->pOrgStream = NULL;
   this->charSet = text::CS_UTF8;
   this->pTempBuf = NULL;
   this->pTempArray = NULL;
   __ClearCharBuf(charBuf);
}

/**
 *  @param [in] pStream -- source stream
 *  @param [in] cs -- character set of the source stream
 */
StreamReader::StreamReader(Stream *pStream, text::charset_t cs)
{
   const sz_t BUFFERSIZE = 64;

   this->pOrgStream = pStream;
   cppmempool::Retain(this->pOrgStream);
   this->charSet = cs;
   // temp buffer to hold raw bytes which are going to be converted a character
   this->pTempBuf = new BinBuf(BUFFERSIZE);
   cppmempool::Retain(this->pTempBuf);
   this->pTempArray = new ArrayByte(BUFFERSIZE);
   cppmempool::Retain(this->pTempArray);
   __ClearCharBuf(charBuf);
}

StreamReader::~StreamReader(void)
{
   Close();
}

void StreamReader::Close()
{
   if (pOrgStream != NULL) {
      cppmempool::Release(pOrgStream);
      pOrgStream = NULL;
   }

   if (pTempArray != NULL) {
      cppmempool::Release(pTempArray);
      pTempArray = NULL;
   }

   if (pTempBuf != NULL) {
      cppmempool::Release(pTempBuf);
      pTempBuf = NULL;
   }
}

int StreamReader::Read(collections::ArrayWChar &buffer)
{
   return Read(buffer, 0, buffer.GetSize());
}

int StreamReader::Read(collections::ArrayWChar &buffer, int offset, int count)
{
   if (count < 0 || offset < 0 || offset >= static_cast<int>(buffer.GetSize()))
      return 0;

   // use the smaller one
   count = cppflib::Min<int>(count, static_cast<int>(buffer.GetSize() - offset));

   int nrCharRead = 0;
   for (int i = 0; i < count; ++i) {
      int c = Read();
      if (c < 0)
         break;

      buffer[i + offset] = static_cast<wc_t>(c);
      ++nrCharRead;
   }

   return nrCharRead;
}

/**
 *  Return character in buf[0] and shift INVALID_CHAR into this buffer
 */
PRIVATE int __ShiftChar(wc_t buf[2])
{
   int c = buf[0];
   buf[0] = buf[1];
   buf[1] = INVALID_CHAR;
   return c;
}

int StreamReader::Read()
{
   int c = -1;

   do {
      if (charBuf[0] != INVALID_CHAR) {
         c = __ShiftChar(charBuf);
         break;
      }

      __ClearCharBuf(charBuf);

      if (ReadOneChar() >= 1) {
         c = __ShiftChar(charBuf);
      }
      
   } while(false);

   return c;
}

/**
 *  Read bytes from stream to buffer
 *
 *  @param [in] stream -- input stream
 *  @param [in, out] buf -- buffer to accept bytes read from stream
 *  @param [in] arrayBuf -- temp buffer
 */
PRIVATE int __FetchBytes(Stream &stream, BinBuf &buf, ArrayByte &arrayBuf)
{
   try {
      int nrByte = stream.Read(arrayBuf);
      if (nrByte > 0)
         buf.AppendData(arrayBuf.GetRawPtr(), nrByte);
      return nrByte;
   }
   catch(BaseException &e) {
      e.GetErrCode(); // to eliminate compiler warning
   }

   return 0;
}

/**
 *  Read one complete character from the stream
 *
 *  @return <= 0 -- no character read
 *  @return 1 -- 1 character read
 *  @return 2 -- 2 characters read (surrogate pairs)
 */
int StreamReader::ReadOneChar()
{
   int nrCharRead = 0;

   switch(charSet) {
      case text::CS_UTF8:
         {
            bool isOK = true;
            while(pTempBuf->GetValidLength() < 1) {
               if (__FetchBytes(*pOrgStream, *pTempBuf, *pTempArray) == 0) {
                  isOK = false;
                  break;
               }
            }

            if (isOK) {
               int numBytes = text::GetNumBytesForUTF8Char(static_cast<char>(*pTempBuf->GetRawBuf()));
               if (numBytes < 0) {
                  isOK = false;
               }
               else if (numBytes == 1) {
                  // no extra bytes needed
                  charBuf[0] = static_cast<wc_t>(*pTempBuf->GetRawBuf());
                  pTempBuf->MoveData2Front(1);
                  nrCharRead = 1;
               }
               else {
                  while(static_cast<int>(pTempBuf->GetValidLength()) < numBytes) {
                     if (__FetchBytes(*pOrgStream, *pTempBuf, *pTempArray) == 0) {
                        isOK = false;
                        break;
                     }
                  }

                  if (isOK) {
                     nrCharRead = text::UTF8ToUTF16(reinterpret_cast<char*>(pTempBuf->GetRawBuf()), numBytes, charBuf, 2);
                     pTempBuf->MoveData2Front(numBytes);
                  }
                  else {
                     pTempBuf->MoveData2Front(pTempBuf->GetValidLength());
                  }

               }
            }
             
         }
         break;
      case text::CS_UTF16LE:
      case text::CS_UTF16BE:
         {
            bool isOK = true;
            while(pTempBuf->GetValidLength() < 2) {
               if (__FetchBytes(*pOrgStream, *pTempBuf, *pTempArray) == 0) {
                  isOK = false;
                  break;
               }
            }

            if (isOK) {
               u8_t * pData = pTempBuf->GetRawBuf();
               int t = 0;
               if (charSet == text::CS_UTF16LE) {
                  t = *pData++;
                  t |= (*pData << 8);
               }
               else {
                  t = (*pData << 8);
                  ++pData;
                  t |= *pData;
               }
               charBuf[0] = static_cast<wc_t>(t);
               nrCharRead = 1;
               pTempBuf->MoveData2Front(2);
            }
         }
         break;
   }

   return nrCharRead;
}

/**
 *  Not supported
 */
i64_t StreamReader::Skip(i64_t charCount)
{
   return 0;
}

/**
 *  Not supported
 */
sz_t StreamReader::CanReadNoBlocking()
{
   return 0;
}


} // end of namespace io

} // end of namespace cppflib
